/*
 * Part of the ACID Project
 * Developed in 2011 by Morgan Taschuk
 */
package acid.plugin.expression;

import acid.plugin.expression.similarity.CalculatePairwiseSimilarity;
import acid.plugin.gui.ChartUrl;
import acid.spring.GeneAliasService;
import acid.spring.GeneExpressionResultService;
import acid.spring.TissueService;
import acid.spring.db.Experiment;
import acid.spring.db.GeneAlias;
import acid.spring.db.GeneExpressionResult;
import acid.spring.db.Tissue;
import cytoscape.CyNode;
import cytoscape.Cytoscape;
import cytoscape.data.CyAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.EmptyResultDataAccessException;

/**
 * Reads the expression data from the database and does the processing necessary.
 * @author morgan
 */
public class ReadExpressionData {

    private GeneAliasService geneAliasService = null;
    private GeneExpressionResultService geneExpressionResultService = null;
    private String geneNameVariable;
    private Map<String, Set<String>> cyIdentifiersToNames;
    private TissueService tissueService;
    protected double maxPVal;
    protected double minFoldChange;

    /**
     * Get the value of minFoldChange
     *
     * @return the value of minFoldChange
     */
    public double getMinFoldChange() {
        return minFoldChange;
    }

    /**
     * Get the value of maxPVal
     *
     * @return the value of maxPVal
     */
    public double getMaxPVal() {
        return maxPVal;
    }

    /**
     * Initialises the connections to the database and other class variables.
     * @param geneNameVariable the String that indicates what the name of the
     */
    public ReadExpressionData(String geneNameVariable, double maxPVal, double minFoldChange) {
        this.geneNameVariable = geneNameVariable;
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath:/jdbc-services.xml");
        BeanFactory factory = context;
        geneAliasService = (GeneAliasService) factory.getBean(GeneAliasService.class);
        geneExpressionResultService = (GeneExpressionResultService) factory.getBean(GeneExpressionResultService.class);
        tissueService = (TissueService) factory.getBean(TissueService.class);
        this.maxPVal = maxPVal;
        this.minFoldChange = minFoldChange;

        cyIdentifiersToNames = getIdentifiersToNames();

    }

    /**
     * Returns a map of Cytoscape identifiers to ACID GeneAliases.
     * @return
     */
    public Map<String, Set<GeneAlias>> getGeneAliases() {
        Map<String, Set<GeneAlias>> cyIdToGeneAlias = new HashMap<String, Set<GeneAlias>>();
        for (String identifier : cyIdentifiersToNames.keySet()) {
            Set<String> geneNames = cyIdentifiersToNames.get(identifier);
            Set<GeneAlias> aliases = new HashSet<GeneAlias>();
            for (String geneName : geneNames) {
                try {
                    GeneAlias ge = geneAliasService.getGeneAliasByAlias(geneName);
                    aliases.add(ge);
                } catch (EmptyResultDataAccessException e) {
//                    System.out.println(geneName + " not found in ACID. ID "
//                            + identifier);
                }
            }
            if (!aliases.isEmpty()) {
                cyIdToGeneAlias.put(identifier, aliases);
            }
        }
        return cyIdToGeneAlias;
    }

    /**
     * Returns a map of Cytoscape identifiers to Cytoscape gene names (the
     * attribute name specified on instantiation).
     * @return
     */
    private Map<String, Set<String>> getIdentifiersToNames() {
        Map<String, Set<String>> cyIdsToNames = new HashMap<String, Set<String>>();

        CyAttributes atts = Cytoscape.getNodeAttributes();

        List nodes = Cytoscape.getCyNodesList();

        for (Object n : nodes) {
            CyNode node = (CyNode) n;
            String identifier = node.getIdentifier();
            List<String> geneNames = atts.getListAttribute(identifier, geneNameVariable);
            Set<String> names = new HashSet<String>(geneNames);
            cyIdsToNames.put(identifier, names);

        }
        return cyIdsToNames;
    }

    /**
     * EucPear measure.Duplicated code alert!
     * @param ge1
     * @param ge2
     * @param maxPVal
     * @param minFoldChange
     * @return
     */
    public double getExpressionSimilarity(Set<GeneAlias> ge1, Set<GeneAlias> ge2,
            double maxPVal, double minFoldChange) {
        List<GeneExpressionResult> exp1 = new ArrayList<GeneExpressionResult>();
        List<GeneExpressionResult> exp2 = new ArrayList<GeneExpressionResult>();
        for (GeneAlias e1 : ge1) {
            exp1.addAll(geneExpressionResultService.queryByGeneAlias(e1));
        }
        for (GeneAlias e2 : ge2) {
            exp2.addAll(geneExpressionResultService.queryByGeneAlias(e2));
        }
        List<Double> uL = new ArrayList<Double>();
        List<Double> vL = new ArrayList<Double>();

        int counter = 0;
        LOOP:
        for (GeneExpressionResult e1 : exp1) {
            for (GeneExpressionResult e2 : exp2) {
                if (e1.getExperimentAnalysis().getAnalysis_id()
                        == e2.getExperimentAnalysis().getAnalysis_id()
                        && e1.getP_value() <= maxPVal && e2.getP_value() <= maxPVal
                        && Math.abs(e1.getFold_change()) >= minFoldChange
                        && Math.abs(e2.getFold_change()) >= minFoldChange && counter < 100) {
                    uL.add(e1.getFold_change());
                    vL.add(e2.getFold_change());
                    counter++;
                }
                if (counter==100) {
                    System.out.println("WTF " + e1.getGeneAlias().getGene_alias_id() + " " +exp1.size()+" and " + e2.getGeneAlias().getGene_alias_id()+" "+exp2.size());
                    break LOOP;
                }
            }
        }

        if (uL.isEmpty() || vL.isEmpty()) {
            return Double.MIN_VALUE;
        }

        double[] u = CalculatePairwiseSimilarity.toDoubleArray(uL);
        double[] v = CalculatePairwiseSimilarity.toDoubleArray(vL);

        return CalculatePairwiseSimilarity.calculateEucPear(u, v);
    }

    /**
     * Pearson measure. Duplicated code alert!
     * @param ge1
     * @param ge2
     * @param maxPVal
     * @param minFoldChange
     * @return
     */
    public double getPearsonExpressionSimilarity(Set<GeneAlias> ge1, Set<GeneAlias> ge2,
            double maxPVal, double minFoldChange) {
        List<GeneExpressionResult> exp1 = new ArrayList<GeneExpressionResult>();
        List<GeneExpressionResult> exp2 = new ArrayList<GeneExpressionResult>();
        for (GeneAlias e1 : ge1) {
            exp1.addAll(geneExpressionResultService.queryByGeneAlias(e1));
        }
        for (GeneAlias e2 : ge2) {
            exp2.addAll(geneExpressionResultService.queryByGeneAlias(e2));
        }
        List<Double> uL = new ArrayList<Double>();
        List<Double> vL = new ArrayList<Double>();

        for (GeneExpressionResult e1 : exp1) {
            for (GeneExpressionResult e2 : exp2) {
                if (e1.getExperimentAnalysis().getAnalysis_id()
                        == e2.getExperimentAnalysis().getAnalysis_id()
                        && e1.getP_value() <= maxPVal && e2.getP_value() <= maxPVal
                        && Math.abs(e1.getFold_change()) >= minFoldChange
                        && Math.abs(e2.getFold_change()) >= minFoldChange) {
                    uL.add(e1.getFold_change());
                    vL.add(e2.getFold_change());
                }
            }
        }

        if (uL.isEmpty() || vL.isEmpty()) {
            return Double.MIN_VALUE;
        }

        double[] u = CalculatePairwiseSimilarity.toDoubleArray(uL);
        double[] v = CalculatePairwiseSimilarity.toDoubleArray(vL);

        return CalculatePairwiseSimilarity.calculateZScorePearson(u, v);
    }

    /**
     * Euclid measure.Duplicated code alert!
     * @param ge1
     * @param ge2
     * @param maxPVal
     * @param minFoldChange
     * @return
     */
    public double getEuclideanExpressionSimilarity(Set<GeneAlias> ge1, Set<GeneAlias> ge2,
            double maxPVal, double minFoldChange) {
        List<GeneExpressionResult> exp1 = new ArrayList<GeneExpressionResult>();
        List<GeneExpressionResult> exp2 = new ArrayList<GeneExpressionResult>();
        for (GeneAlias e1 : ge1) {
            exp1.addAll(geneExpressionResultService.queryByGeneAlias(e1));
        }
        for (GeneAlias e2 : ge2) {
            exp2.addAll(geneExpressionResultService.queryByGeneAlias(e2));
        }
        List<Double> uL = new ArrayList<Double>();
        List<Double> vL = new ArrayList<Double>();

        for (GeneExpressionResult e1 : exp1) {
            for (GeneExpressionResult e2 : exp2) {
                if (e1.getExperimentAnalysis().getAnalysis_id()
                        == e2.getExperimentAnalysis().getAnalysis_id()
                        && e1.getP_value() <= maxPVal && e2.getP_value() <= maxPVal
                        && Math.abs(e1.getFold_change()) >= minFoldChange
                        && Math.abs(e2.getFold_change()) >= minFoldChange) {
                    uL.add(e1.getFold_change());
                    vL.add(e2.getFold_change());
                }
            }
        }

        if (uL.isEmpty() || vL.isEmpty()) {
            return Double.MIN_VALUE;
        }

        double[] u = CalculatePairwiseSimilarity.toDoubleArray(uL);
        double[] v = CalculatePairwiseSimilarity.toDoubleArray(vL);

        return CalculatePairwiseSimilarity.calculateZScoreEuclid(u, v);
    }

    public Map<String, Double> getExpressionValues(Set<GeneAlias> ge) {
        List<GeneExpressionResult> expressions = new ArrayList<GeneExpressionResult>();
        for (GeneAlias e1 : ge) {
            expressions.addAll(geneExpressionResultService.queryByGeneAlias(e1));
        }

        Map<String, Double> analysisIdToFoldChange = new TreeMap<String, Double>();
        Map<Integer, Integer> duplicates = new TreeMap<Integer, Integer>();

        for (GeneExpressionResult e : expressions) {
            int analysisId = e.getExperimentAnalysis().getAnalysis_id();
            Integer dupes = duplicates.get(analysisId);
            if (dupes == null) {
                if (addItem(analysisIdToFoldChange, new Integer(analysisId).toString(), e, e.getFold_change())) {
                    duplicates.put(analysisId, 1);
                }
            } else {
                String id = new Integer(analysisId).toString();
                Double avg = analysisIdToFoldChange.get(id);
                if (addItem(analysisIdToFoldChange, new Integer(analysisId).toString(),
                        e, iterativeAverage(avg, duplicates.get(analysisId), e.getFold_change()))) {
                    duplicates.put(analysisId, ++dupes);
                }
            }
        }
        return analysisIdToFoldChange;
    }

    private boolean addItem(Map<String, Double> map, String analysisId, GeneExpressionResult e, double fc) {
        if (fc > minFoldChange && e.getP_value() < maxPVal) {
            map.put(analysisId, fc);
            return true;
        }
        return false;
    }

    /**
     *
     * @param avg
     * @param n
     * @param newVal
     * @return
     */
    public static double iterativeAverage(double avg, long n, double newVal) {
        double newAvg = (n / (n + 1)) * (avg + (newVal / n));
        return newAvg;
    }

    /**
     * Retrieves the Google chart URL for all available tissues and experiments.
     * @param ge the Gene Alias
     * @param maxPVal the maximum p value
     * @param minFoldChange the minimum fold change
     * @return the URL with the Google chart
     */
    public String getUrl(Set<GeneAlias> ge, double maxPVal, double minFoldChange) {
        List<GeneExpressionResult> expressions = new ArrayList<GeneExpressionResult>();

        for (GeneAlias g : ge) {
            expressions.addAll(geneExpressionResultService.queryByGeneAlias(g));
        }
        Double[] results = countResults(expressions, maxPVal, minFoldChange);
        if (results == null) {
            return null;
        }
        return ChartUrl.basicChart(results);
    }

    /**
     * Checks not only for the given tissue, but also any sub-tissues. For example,
     * a tissue with the name of "skeletal muscle" will also find
     * "skeletal muscle - deltoid";
     *
     *
     * @param ge
     * @param maxPVal
     * @param minFoldChange
     * @param tissue
     * @return
     */
    public String getTissueUrl(Set<GeneAlias> ge, double maxPVal,
            double minFoldChange, Tissue tissue) {

        List<Tissue> tissues = tissueService.matchTissueType(tissue.getName());
        List<GeneExpressionResult> expressions = new ArrayList<GeneExpressionResult>();

        for (GeneAlias g : ge) {
            for (Tissue t : tissues) {
                expressions.addAll(geneExpressionResultService.queryByTissue(g, t));
            }
        }

        Double[] results = countResults(expressions, maxPVal, minFoldChange);
        if (results == null) {
            return null;
        }
        return ChartUrl.titledChart(results, tissue.getName());

    }

    public String getExperimentUrl(Set<GeneAlias> ge, double maxPVal,
            double minFoldChange, Experiment experiment) {
        List<GeneExpressionResult> expressions = new ArrayList<GeneExpressionResult>();

        for (GeneAlias g : ge) {
            expressions.addAll(geneExpressionResultService.queryByGeneAlias(g));
        }
        Double[] results = countResults(expressions, maxPVal, minFoldChange);
        if (results == null) {
            return null;
        }
        return ChartUrl.titledChart(results, experiment.getName());

    }

    private Double[] countResults(List<GeneExpressionResult> expressions,
            double maxPVal, double minFoldChange) {
        double over = 0;
        double under = 0;
        double unchanged = 0;
        for (GeneExpressionResult er : expressions) {
            //System.out.println(er);
            double foldChange = er.getFold_change();
            if (er.getP_value() <= maxPVal) {
                if (Math.abs(foldChange) < minFoldChange) {
                    unchanged++;
                } else if (foldChange > 0) {
                    over++;
                } else if (foldChange < 0) {
                    under++;
                }
            }
        }

        if (over == 0 && under == 0 && unchanged == 0) {
            return null;
        }
        return new Double[]{
                    over, under, unchanged
                };
    }

    /**
     * Get the value of geneVarName
     *
     * @return the value of geneVarName
     */
    public String getGeneVarName() {
        return geneNameVariable;
    }

    /**
     * Set the value of geneVarName
     *
     * @param geneVarName new value of geneVarName
     */
    public void setGeneVarName(String geneVarName) {
        this.geneNameVariable = geneVarName;
    }
}
